iT邦幫忙

2023 iThome 鐵人賽

DAY 24
0

前幾天我們看過了 WebSockets 的引擎實作,使用方式,以及背後的運作邏輯

今天我們來看看 Frame 的實作細節

簽名以及註解

/**
 * A frame received or ready to be sent. It is not reusable and not thread-safe
 * @property fin is it final fragment, should be always `true` for control frames and if no fragmentation is used
 * @property frameType enum value
 * @property data - a frame content or fragment content
 * @property disposableHandle could be invoked when the frame is processed
 */
public actual sealed class Frame actual constructor(
    public actual val fin: Boolean,
    public actual val frameType: FrameType,
    public actual val data: ByteArray,
    public actual val disposableHandle: DisposableHandle,
    public actual val rsv1: Boolean,
    public actual val rsv2: Boolean,
    public actual val rsv3: Boolean
) 

註解內有針對參數做說明,不過 rsv1 rsv2 rsv3 這三個參數沒有

原因是因為這三個參數是定義在 RFC 6455 的 Base Framing Protocol 裡面

RSV1, RSV2, RSV3: 1 bit each
MUST be 0 unless an extension is negotiated that defines meanings
for non-zero values. If a nonzero value is received and none of
the negotiated extensions defines the meaning of such a nonzero
value, the receiving endpoint MUST Fail the WebSocket Connection.

之後我們看內容的部分,首先是

/**  
* Frame content  
*/  
public val buffer: ByteBuffer = ByteBuffer.wrap(data)

這邊定義了 Frame 內容儲存的位置

接著就是各種 Frame 的定義,比方說我們之前看過的 Frame.Text

/**
 * Represents an application level text frame.
 * In a RAW web socket session a big text frame could be fragmented
 * (separated into several text frames so they have [fin] = false except the last one).
 * Please note that a boundary between fragments could be in the middle of multi-byte (unicode) character
 * so don't apply String constructor to every fragment but use decoder loop instead of concatenate fragments first.
 * Note that usually there is no need to handle fragments unless you have a RAW web socket session.
 */
public actual class Text actual constructor(
	fin: Boolean,
	data: ByteArray,
	rsv1: Boolean,
	rsv2: Boolean,
	rsv3: Boolean
) : Frame(fin, FrameType.TEXT, data, NonDisposableHandle, rsv1, rsv2, rsv3) {

	public actual constructor(fin: Boolean, data: ByteArray) : this(fin, data, false, false, false)

	public actual constructor(text: String) : this(true, text.toByteArray())

	public actual constructor(fin: Boolean, packet: ByteReadPacket) : this(fin, packet.readBytes())

	public constructor(fin: Boolean, buffer: ByteBuffer) : this(fin, buffer.moveToByteArray())
}

除了 Frame.Text 之外,還有一些其他的類別,像是 Frame.Binary

/**
 * Represents an application level binary frame.
 * In a RAW web socket session a big text frame could be fragmented
 * (separated into several text frames so they have [fin] = false except the last one).
 * Note that usually there is no need to handle fragments unless you have a RAW web socket session.
 */
public actual class Binary actual constructor(
	fin: Boolean,
	data: ByteArray,
	rsv1: Boolean,
	rsv2: Boolean,
	rsv3: Boolean
) : Frame(fin, FrameType.BINARY, data, NonDisposableHandle, rsv1, rsv2, rsv3) {
	public constructor(fin: Boolean, buffer: ByteBuffer) : this(fin, buffer.moveToByteArray())

	public actual constructor(fin: Boolean, data: ByteArray) : this(fin, data, false, false, false)

	public actual constructor(fin: Boolean, packet: ByteReadPacket) : this(fin, packet.readBytes())
}

還有之前看過的 Frame.Close

/**
 * Represents a low-level level close frame. It could be sent to indicate web socket session end.
 * Usually there is no need to send/handle it unless you have a RAW web socket session.
 */
public actual class Close actual constructor(
	data: ByteArray
) : Frame(true, FrameType.CLOSE, data, NonDisposableHandle, false, false, false) {

	public actual constructor(reason: CloseReason) : this(
		buildPacket {
			writeShort(reason.code)
			writeText(reason.message)
		}
	)

	public actual constructor(packet: ByteReadPacket) : this(packet.readBytes())
	public actual constructor() : this(Empty)

	public constructor(buffer: ByteBuffer) : this(buffer.moveToByteArray())
}

這個和前兩個就有些不同,直接就將 fin 設置為 true 並且寫入 CloseReason

接著我們看到官網教學裡面沒有提到,但是很有趣的兩個類別

首先是 Frame.Ping,根據名稱很容易想到這個類別是用來做 ping-pong 測試用的

/**
 * Represents a low-level ping frame. Could be sent to test connection (peer should reply with [Pong]).
 * Usually there is no need to send/handle it unless you have a RAW web socket session.
 */
public actual class Ping actual constructor(
	data: ByteArray
) : Frame(true, FrameType.PING, data, NonDisposableHandle, false, false, false) {
	public actual constructor(packet: ByteReadPacket) : this(packet.readBytes())
	public constructor(buffer: ByteBuffer) : this(buffer.moveToByteArray())
}

這邊我們看到,fin 一樣是固定設置為 true

這也很合理,畢竟 ping-pong 測試不需要在裡面加上大量的內容

接著 Frame.Ping的,當然是 Frame.Pong

/**
 * Represents a low-level pong frame. Should be sent in reply to a [Ping] frame.
 * Usually there is no need to send/handle it unless you have a RAW web socket session.
 */
public actual class Pong actual constructor(
	data: ByteArray,
	disposableHandle: DisposableHandle
) : Frame(true, FrameType.PONG, data, disposableHandle, false, false, false) {
	public actual constructor(packet: ByteReadPacket) : this(packet.readBytes(), NonDisposableHandle)
	public constructor(
		buffer: ByteBuffer,
		disposableHandle: DisposableHandle = NonDisposableHandle
	) : this(buffer.moveToByteArray(), disposableHandle)

	public constructor(buffer: ByteBuffer) : this(buffer.moveToByteArray(), NonDisposableHandle)
}

這邊和 Frame.Ping 相比,改變了一點設置

允許使用者將 disposableHandle 抽換成 NonDisposableHandle 以外的實作

DisposableHandle 如預料的是一個非常小的介面

/**
 * A handle to an allocated object that can be disposed to make it eligible for garbage collection.
 */
public fun interface DisposableHandle {
    /**
     * Disposes the corresponding object, making it eligible for garbage collection.
     * Repeated invocation of this function has no effect.
     */
    public fun dispose()
}

這也是閱讀程式碼挺有趣的地方,有時候常常會看到文件上沒有寫,或者是相對我們可能會忽略的功能。

這時候看程式碼就會直接找到對應功能,並且知道如何使用。

接著我們往下看,看到這邊還有覆寫 toString

    override fun toString(): String = "Frame $frameType (fin=$fin, buffer len = ${data.size})"

以及實作了一個 copy

public actual fun copy(): Frame = byType(fin, frameType, data.copyOf(), rsv1, rsv2, rsv3)

這邊所呼叫的 byType,定義於 companion object  裡面

public actual companion object {
	private val Empty: ByteArray = ByteArray(0)

	/**
	 * Create a particular [Frame] instance by frame type.
	 */
	public actual fun byType(
		fin: Boolean,
		frameType: FrameType,
		data: ByteArray,
		rsv1: Boolean,
		rsv2: Boolean,
		rsv3: Boolean
	): Frame = when (frameType) {
		FrameType.BINARY -> Binary(fin, data, rsv1, rsv2, rsv3)
		FrameType.TEXT -> Text(fin, data, rsv1, rsv2, rsv3)
		FrameType.CLOSE -> Close(data)
		FrameType.PING -> Ping(data)
		FrameType.PONG -> Pong(data, NonDisposableHandle)
	}
}

到這邊我們就看過了 Frame  的內容了。


上一篇
Day 23:webSocket 的 Frame 以及對應的各個函數
下一篇
Day 25:定義自己的 Connection 以及存取 DefaultWebSocketSession
系列文
深入解析 Kotlin 專案 Ktor 的程式碼,探索 Ktor 的強大功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言